package io.lvdaxian.handwriting.concurrency;

import cn.hutool.core.util.StrUtil;
import lombok.Getter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.stream.Collectors;


/**
 * @author lihh
 * @descirption 模拟令牌桶算法，从而控制会话并发
 */
@Getter
public class ConcurrencyBucket {
  /* 可用的元素 下标 */
  private Integer usableIndex = 0;
  /* 本身就是一个算法桶 */
  private String[] bucket = null;
  /* 表示实例本身 */
  private static volatile ConcurrencyBucket INSTANCE;
  
  private final ReentrantLock concurrencyBucketLock = new ReentrantLock();
  
  /**
   * 通过单例模式 拿到并发桶 实例
   *
   * @return 返回一个桶实例
   * @author lihh
   */
  public static ConcurrencyBucket getInstance() {
    if (INSTANCE == null) {
      synchronized (ConcurrencyBucket.class) {
        if (INSTANCE == null) {
          INSTANCE = new ConcurrencyBucket();
        }
      }
    }
    
    return INSTANCE;
  }
  
  /**
   * @descirption 表示初始化桶
   * @author lihh
   */
  public void initializationBucket(Integer concurrencyCount) {
    try {
      concurrencyBucketLock.lock();
      bucket = new String[concurrencyCount];
    } finally {
      concurrencyBucketLock.unlock();
    }
  }
  
  /**
   * @descirption 判断 并发桶是否满了
   * @author lihh
   */
  public boolean isFullConcurrencyBucket() {
    try {
      concurrencyBucketLock.lock();
      // 并发已经满了 无法再添加了
      return isFullBucket();
    } finally {
      concurrencyBucketLock.unlock();
    }
  }
  
  /**
   * @param token 用户登录token
   * @descirption 当用户登录时，拿token 换取桶令牌
   * @author lihh
   */
  public boolean joinConcurrencyBucket(String token) {
    if (isFullBucket()) {
      return false;
    }
    
    try {
      concurrencyBucketLock.lock();
      bucket[usableIndex++] = token;
      return true;
    } finally {
      concurrencyBucketLock.unlock();
    }
  }
  
  /**
   * @return true 表示空桶/ false 反之不是
   * @descirption 判断是否是一个空桶
   * @author lihh
   */
  private boolean isEmptyBucket() {
    return 0 == usableIndex;
  }
  
  /**
   * @return 返回是否满的状态  true 表示满了/ false 表示不满
   * @descirption 判断桶是否已经满了
   * @author lihh
   */
  private boolean isFullBucket() {
    return usableIndex == bucket.length;
  }
  
  /**
   * @param token         传递的tk
   * @param currentBucket 当前遍历的bucket
   * @return 返回索引
   * @descirption 根据tk 寻找索引
   * @author lihh
   */
  private int findIdxByToken(String token, String[] currentBucket) {
    
    for (int idx = 0; idx < currentBucket.length; idx += 1) {
      if (StrUtil.equals(currentBucket[idx], token)) {
        return idx;
      }
    }
    return -1;
  }
  
  /**
   * @param token 用户token
   * @descirption 当有人退出登录时，从桶中删除token
   * @author lihh
   */
  public void exitConcurrencyBucket(String token) {
    try {
      concurrencyBucketLock.lock();
      if (isEmptyBucket() || !Arrays.asList(bucket).contains(token)) {
        return;
      }
      
      // 找到当前tk 的对应位置
      int idx = findIdxByToken(token, bucket);
      if (-1 == idx) {
        return;
      }
      
      // 移动元素
      bucket[idx] = bucket[bucket.length - 1];
      bucket[bucket.length - 1] = null;
      usableIndex -= 1;
    } finally {
      concurrencyBucketLock.unlock();
    }
  }
  
  
  /**
   * 如果是新的桶长度大于老的桶，直接赋值拷贝
   *
   * @param newMaxCount 新的最大长度
   * @author lihh
   */
  private void newIsGreaterThanOldHandler(Integer newMaxCount) {
    String[] newBucket = new String[newMaxCount];
    
    System.arraycopy(bucket, 0, newBucket, 0, bucket.length);
    bucket = newBucket;
  }
  
  /**
   * 如果是新的桶长度小于老的桶长度，剔除一部分token
   *
   * @param newMaxCount          新的桶长度
   * @param currentLoginToken    当前登录的token
   * @param excludeTokenCallback 要剔除的token 回调
   * @author lihh
   */
  private void newIsLessThanOldHandler(Integer newMaxCount, String currentLoginToken, Consumer<List<String>> excludeTokenCallback) {
    String[] newBucket = new String[newMaxCount];
    
    System.arraycopy(bucket, 0, newBucket, 0, newBucket.length);
    // 这是排除的 tk
    List<String> willLogoutTokens = new ArrayList<>(Arrays.asList(Arrays.copyOfRange(bucket, newMaxCount, bucket.length)));
    
    // 如果当前tk 存在于 新桶中
    int idx = findIdxByToken(currentLoginToken, newBucket);
    // 表示当前tk 并不在数组中
    if (-1 == idx) {
      // 确保 当前tk 不在集合中
      willLogoutTokens = willLogoutTokens.stream()
          .filter(item ->
              !StrUtil.equals(item, currentLoginToken))
          .collect(Collectors.toList());
      willLogoutTokens.add(newBucket[newBucket.length - 1]);
      newBucket[newBucket.length - 1] = currentLoginToken;
    }
    
    bucket = newBucket;
    usableIndex = newMaxCount;
    
    if (!willLogoutTokens.isEmpty()) {
      excludeTokenCallback
          .accept(
              willLogoutTokens.stream()
                  .filter(l -> !StrUtil.isEmpty(l))
                  .collect(Collectors.toList())
          );
    }
  }
  
  /**
   * 将新的桶 以及旧的桶 进行合并
   *
   * @param newMaxCount          新的桶最大长度
   * @param currentLoginToken    当前登录的token
   * @param excludeTokenCallback 排除令牌的回调，如果桶大小变小后 有可能一些token无法保留了
   * @author lihh
   */
  public void reMergeConcurrencyBucket(Integer newMaxCount, String currentLoginToken, Consumer<List<String>> excludeTokenCallback) {
    try {
      concurrencyBucketLock.lock();
      // 如果现在的有桶大小 不变的情况下
      if (bucket.length == newMaxCount) {
        return;
      }
      
      if (newMaxCount > bucket.length) {
        newIsGreaterThanOldHandler(newMaxCount);
      } else {
        newIsLessThanOldHandler(newMaxCount, currentLoginToken, excludeTokenCallback);
      }
    } finally {
      concurrencyBucketLock.unlock();
    }
  }
}
